// *** LICENSE HEADER ***
// Filename: ./serec.cpp
// This file is part of Nodewarz 0.1
// Nodewarz is a 3d OpenGL strategy game
// Copyright (C) 2007 CZestmyr
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *** END LICENSE HEADER ***
#include "serec.h"

serec::serec(logger* newLogger){
    theLogger = newLogger;
}

serec::~serec(){}

bool serec::Init(Uint16 port){
    SDLNet_Init();

    sock = SDLNet_UDP_Open(port);
    if (!sock) {
        SDLNet_Quit();
        return false;
    }
    return true;
}

void serec::Update(Sint32 dt){
    //Go through all the peers and (Re-)send the packets, if applicable
    //Also remove packets that couldn't reach the desired destination
    map<IPaddress, peerData>::iterator peersBegin = peers.begin();
    map<IPaddress, peerData>::iterator peersEnd = peers.end();
    while (peersBegin != peersEnd){
        peerData& peer = (*peersBegin).second;
        //If there is a packet in pendingPackets
        if (peer.pendingPackets.size() > 0) {
            packetData& pd = peer.pendingPackets[0];
            pd.Update(dt);
            //If the packet is ready for another try
            if (pd.GetWaitTime() <= 0) {
                //Try to send it again if there are some tries left
                if (pd.HasRetriesLeft()) {
                    pd.NewTry();
                    SDLNet_UDP_Send(sock, -1, pd.GetPacket());
                    peer.lastSentPacketNumber = SDLNet_Read32(&(pd.GetPacket()->data[1]));
                } else {
                    //Remove the packet, because it didn't deliver and there are no more retries left
                    peer.pendingPackets.erase(peer.pendingPackets.begin());
                }
            }
        }
        ++peersBegin;
    }
}

void serec::Deinit(){
    receivedPackets.clear();

    peers.clear();

    SDLNet_UDP_Close(sock);

    SDLNet_Quit();
}

int serec::GetUnsentMessageNumber(Uint32 host, Uint16 port){
    IPaddress addr;
    addr.host = host;
    addr.port = port;

    if (peers.count(addr) > 0)
        return peers[addr].pendingPackets.size();
    else return 0;
}

void serec::FlushUnsentMessages(Uint32 host, Uint16 port){
    IPaddress addr;
    addr.host = host;
    addr.port = port;

    peers[addr].pendingPackets.clear();
}

void serec::TryToSendWholeQueue(){
    map<IPaddress, peerData>::iterator peersBegin = peers.begin();
    map<IPaddress, peerData>::iterator peersEnd = peers.end();

    //Traverse the whole message queue for all peers and send all unsent messages
    while (peersBegin != peersEnd){
        vector<packetData>::iterator packetsBegin = (*peersBegin).second.pendingPackets.begin();
        vector<packetData>::iterator packetsEnd = (*peersBegin).second.pendingPackets.end();

        while (packetsBegin != packetsEnd){
            SDLNet_UDP_Send(sock, -1, (*packetsBegin).GetPacket());
            ++packetsBegin;
        }
        ++peersBegin;
    }
}

void serec::Send(Uint32 host, Uint16 port, const char* message){
    //Refuse to send the packet if the message is too long
    if (strlen(message) > _SEREC_MAX_MESSAGE_LENGTH)
        return;

    //Construct IPaddress struct
    IPaddress addr;
    addr.host = host;
    addr.port = port;

    //Construct the UDP packet
    peerData& peer = peers[addr];

    packetData pd(strlen(message) + 6);
    pd.SetMessage(message);
    pd.SetPacketType(true);
    pd.SetPacketNumber(peer.GetNextPacketNumber());
    pd.SetAddress(addr);

    //Log this packet
    theLogger->log(LL_DEBUG, "Sending DATA packet:");
    theLogger->log(LL_DEBUG, message);

    //Add the packet to the pending packets of the peer
    peer.pendingPackets.push_back(pd);
}

int serec::Receive(){
    receivedPackets.clear();

    //Container for the incoming packet
    packetData incomingPacket(_SEREC_MAX_PACKET_SIZE);
    int receivedPacketNumber = 0;

    while (SDLNet_UDP_Recv(sock, incomingPacket.GetPacket()) == 1){
        peerData& peer = peers[incomingPacket.GetAddress()];

        //Get the packet number
        Uint32 packetNumber = incomingPacket.GetPacketNumber();
        if (!incomingPacket.IsData()) { //If the packet type is 0 (ACK packet)
            //If the packet number is the last packet sent, stop sending the packet (it was successful)
            if (packetNumber == peer.lastSentPacketNumber){
                vector<packetData>::iterator packetsBegin = peer.pendingPackets.begin();
                //Check for duplicate ACKs
                if (packetsBegin != peer.pendingPackets.end() && packetNumber == (*packetsBegin).GetPacketNumber()) {
                    peer.pendingPackets.erase(packetsBegin);
                    //Log this packet
                    theLogger->log(LL_DEBUG, "Incoming ACK packet");
                }
            } //Else do nothing (drop the ack packet)
        } else
        if (incomingPacket.IsData()) { //If the packet type is 1 (data packet)
            //Check for duplicate packets
            if (packetNumber != peer.lastReceivedPacketNumber) {
                peer.lastReceivedPacketNumber = packetNumber;
                receivedPackets.push_back(incomingPacket);
                ++receivedPacketNumber;
                //Log this packet
                theLogger->log(LL_DEBUG, "Incoming DATA packet");
                theLogger->log(LL_DEBUG, (char*)&incomingPacket.GetPacket()->data[5]);
            }
            //Send an acknowledgement packet to the sender (even in case of duplicate packets)
            packetData ackPacket(8);
            ackPacket.SetAddress(incomingPacket.GetAddress());
            ackPacket.SetPacketType(false);
            ackPacket.SetPacketNumber(incomingPacket.GetPacketNumber());
            SDLNet_UDP_Send(sock, -1, ackPacket.GetPacket());
            //Log this packet
            theLogger->log(LL_DEBUG, "Sending ACK packet");
        }
    }

    receivedPacketsIterator = receivedPackets.begin();
    return receivedPacketNumber;
}

void serec::GetMessage(Uint32& host, Uint16& port, char* message){
    if (receivedPacketsIterator != receivedPackets.end()){
        host = (*receivedPacketsIterator).GetPacket()->address.host;
        port = (*receivedPacketsIterator).GetPacket()->address.port;
        strcpy_safe_my(message, (const char*)&((*receivedPacketsIterator).GetPacket()->data[5]), _SEREC_MAX_MESSAGE_LENGTH+1);
    }
    ++receivedPacketsIterator;
}
